Imports Microsoft.VisualBasic
Imports System
Imports System.Runtime.InteropServices
Imports System.Runtime.InteropServices.ComTypes
Imports System.IO

Namespace C4F.DevKit.PreviewHandler.PreviewHandlerHost
    ''' <summary>
    ''' Very simple wrapper class implementation for the IStream interface.
    ''' <remarks>Does not support advanced operations like the Revert() operation, options specified in Commit(), basic stat values, etc.</remarks>
    ''' </summary>
    Friend NotInheritable Class StreamWrapper
        Implements IStream
#Region "Constants"

        Private Const POSITION_NOT_SET As Long = -1
        Private Const STG_E_INVALIDFUNCTION As Integer = 32774
        Private Const CHUNK As Integer = 4096

        Private Const METHOD_NOT_SUPPORTED As String = "Method not supported."
        Private Const UNKNOWN_ERROR As String = "Unknown error."

#End Region

#Region "Private Data"

        Private _indexPosition As Long = POSITION_NOT_SET
        Private ReadOnly _stream As Stream = Nothing

#End Region

#Region "Constructor"

        ''' <summary>
        ''' Initializes a new instance of the StreamWrapper with the specified input stream.
        ''' </summary>
        ''' <param name="stream">The stream being wrapped.</param>
        Friend Sub New(ByVal stream As Stream)
            _indexPosition = POSITION_NOT_SET
            _stream = stream
        End Sub

#End Region

#Region "IStream Members"

        Public Sub Clone(<System.Runtime.InteropServices.Out()> ByRef ppstm As IStream) Implements IStream.Clone
            ' Operation not supported
            ppstm = Nothing
            Throw New ExternalException(METHOD_NOT_SUPPORTED, STG_E_INVALIDFUNCTION)
        End Sub

        Public Sub Commit(ByVal grfCommitFlags As Integer) Implements IStream.Commit
            _stream.Flush()
        End Sub

        Public Sub CopyTo(ByVal pstm As IStream, ByVal cb As Long, ByVal pcbRead As IntPtr, ByVal pcbWritten As IntPtr) Implements IStream.CopyTo
            Dim buffer As Byte() = New Byte(CHUNK - 1) {}
            Dim written As Long = 0
            Dim read As Integer = 0

            If cb <> 0 Then
                SetSizeToPosition()
                Do
                    Dim count As Integer = CHUNK
                    If written + CHUNK > cb Then
                        count = CInt(Fix(cb - written))
                    End If

                    If (read = _stream.Read(buffer, 0, count)) = 0 Then
                        Exit Do
                    End If

                    pstm.Write(buffer, read, IntPtr.Zero)
                    written += read

                Loop While written < cb
            End If

            If pcbRead <> IntPtr.Zero Then
                Marshal.WriteInt64(pcbRead, written)
            End If

            If pcbWritten <> IntPtr.Zero Then
                Marshal.WriteInt64(pcbWritten, written)
            End If
        End Sub

        Public Sub LockRegion(ByVal libOffset As Long, ByVal cb As Long, ByVal dwLockType As Integer) Implements IStream.LockRegion
            ' Operation not supported
            Throw New ExternalException(METHOD_NOT_SUPPORTED, STG_E_INVALIDFUNCTION)
        End Sub

        Public Sub Read(ByVal pv As Byte(), ByVal cb As Integer, ByVal pcbRead As IntPtr) Implements IStream.Read
            Dim read As Integer = 0
            If cb <> 0 Then
                SetSizeToPosition()
                read = _stream.Read(pv, 0, cb)
            End If

            If pcbRead <> IntPtr.Zero Then
                Marshal.WriteInt32(pcbRead, read)
            End If
        End Sub

        Public Sub Revert() Implements IStream.Revert
            Throw New ExternalException(METHOD_NOT_SUPPORTED, STG_E_INVALIDFUNCTION)
        End Sub

        Public Sub Seek(ByVal dlibMove As Long, ByVal dwOrigin As Integer, ByVal plibNewPosition As IntPtr) Implements IStream.Seek
            Dim newPosition As Long = 0
            Dim seekOrigin As SeekOrigin = seekOrigin.Begin

            Try
                ' attempt to cast parameter
                seekOrigin = CType(dwOrigin, SeekOrigin)
            Catch
                Throw New ExternalException(UNKNOWN_ERROR, STG_E_INVALIDFUNCTION)
            End Try

            If _stream.CanWrite Then
                Select Case seekOrigin
                    Case seekOrigin.Begin
                        newPosition = dlibMove

                    Case seekOrigin.Current
                        newPosition = _indexPosition
                        If newPosition = POSITION_NOT_SET Then
                            newPosition = _stream.Position
                        End If

                        newPosition += dlibMove

                    Case seekOrigin.End
                        newPosition = _stream.Length + dlibMove

                    Case Else
                        ' should never happen
                        Throw New ExternalException(UNKNOWN_ERROR, STG_E_INVALIDFUNCTION)
                End Select

                If newPosition > _stream.Length Then
                    _indexPosition = newPosition
                Else
                    _stream.Position = newPosition
                    _indexPosition = POSITION_NOT_SET
                End If
            Else
                Try
                    newPosition = _stream.Seek(dlibMove, seekOrigin)
                Catch e1 As ArgumentException
                    Throw New ExternalException(UNKNOWN_ERROR, STG_E_INVALIDFUNCTION)
                End Try

                _indexPosition = POSITION_NOT_SET
            End If

            If plibNewPosition <> IntPtr.Zero Then
                Marshal.WriteInt64(plibNewPosition, newPosition)
            End If
        End Sub

        Public Sub SetSize(ByVal libNewSize As Long) Implements IStream.SetSize
            _stream.SetLength(libNewSize)
        End Sub

        Public Sub Stat(<System.Runtime.InteropServices.Out()> ByRef pstatstg As System.Runtime.InteropServices.ComTypes.STATSTG, ByVal grfStatFlag As Integer) Implements IStream.Stat
            pstatstg = New System.Runtime.InteropServices.ComTypes.STATSTG()
            pstatstg.cbSize = _stream.Length
        End Sub

        Public Sub UnlockRegion(ByVal libOffset As Long, ByVal cb As Long, ByVal dwLockType As Integer) Implements IStream.UnlockRegion
            ' Operation not supported
            Throw New ExternalException(METHOD_NOT_SUPPORTED, STG_E_INVALIDFUNCTION)
        End Sub

        Public Sub Write(ByVal pv As Byte(), ByVal cb As Integer, ByVal pcbWritten As IntPtr) Implements IStream.Write
            If cb <> 0 Then
                SetSizeToPosition()
                _stream.Write(pv, 0, cb)
            End If

            If pcbWritten <> IntPtr.Zero Then
                Marshal.WriteInt32(pcbWritten, cb)
            End If
        End Sub

#End Region

#Region "Private Methods"

        Private Sub SetSizeToPosition()
            If _indexPosition <> POSITION_NOT_SET Then
                ' position requested greater than current length?
                If _indexPosition > _stream.Length Then
                    ' expand stream
                    _stream.SetLength(_indexPosition)
                End If

                ' set new position
                _stream.Position = _indexPosition
                _indexPosition = POSITION_NOT_SET
            End If
        End Sub

#End Region
    End Class
End Namespace
